/*
 * arp.c
 *
 *  Created on: Jun 24, 2010
 *      Author: fify
 */
#include <string.h>
#include <netinet/in.h>

#include "arp.h"
#include "net/linux/if_ether.h"
#include "net/utils/utils.h"
#include "net/utils/color.h"

#include "net.h"
#include "net/tap.h"

#define IP_ALEN 4

static void build_arp_hdr(struct arphdr *pkt, __be16 ar_op,
		struct mac_addr *sender_mac, unsigned char sender_ip[],
		struct mac_addr *target_mac, unsigned char target_ip[]);
static int reply_arp(struct TAPState *s, struct iovec *iov);

/*
 * Hardware type: Ethernet
 * Protocol type: ARP
 * Hardware address length: 6 (MAC address)
 * Protocol address length: 4 (IP protocol)
 * Operation: request OR reply
 * Sender MAC Address: ?
 * Sender IP Address: ?
 * Target MAC Address: ?
 * Target IP Address: ?
 */
static void build_arp_hdr(struct arphdr *pkt, __be16 ar_op,
		struct mac_addr *sender_mac, unsigned char sender_ip[],
		struct mac_addr *target_mac, unsigned char target_ip[])
{
	memcpy(pkt->ar_sha, (char *)sender_mac, ETH_ALEN);
	memcpy(pkt->ar_sip, (char *)sender_ip, IP_ALEN);
	memcpy(pkt->ar_tha, (char *)target_mac, ETH_ALEN);
	memcpy(pkt->ar_tip, (char *)target_ip, IP_ALEN);

	pkt->ar_op = htons(ar_op);

	pkt->ar_hrd = htons(ETH_P_802_3);
	pkt->ar_pro = htons(ETH_P_IP);
	pkt->ar_hln = ETH_ALEN;
	pkt->ar_pln = IP_ALEN;
}

void build_arp_request(struct arp_packet *pkt,
		struct mac_addr *sender_mac, unsigned char sender_ip[],
		struct mac_addr *target_mac, unsigned char target_ip[])
{
	build_eth_hdr(&pkt->m_eth, target_mac, sender_mac, ETH_P_ARP);
	build_arp_hdr(&pkt->m_arp, ARPOP_REQUEST, sender_mac, sender_ip, target_mac, target_ip);
}

void build_arp_reply(struct arp_packet *pkt,
		struct mac_addr *sender_mac, unsigned char sender_ip[],
		struct mac_addr *target_mac, unsigned char target_ip[])
{
	build_eth_hdr(&pkt->m_eth, target_mac, sender_mac, ETH_P_ARP);
	build_arp_hdr(&pkt->m_arp, ARPOP_REPLY, sender_mac, sender_ip, target_mac, target_ip);
}

static int send_reply(struct TAPState *s, struct arp_packet *arp, struct iovec *iov)
{
	struct arp_packet *reply;

	printf("Original packet: \n");
	print_packet((uint8_t *)arp, iov->iov_len, F_GREEN);
	//reply = new struct arp_packet;
	reply = (struct arp_packet *)malloc(sizeof(struct arp_packet));

	unsigned char reply_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};

	print_packet((uint8_t *)arp, iov->iov_len, F_BLUE);
	build_arp_reply(reply, (struct mac_addr *)reply_mac, arp->m_arp.ar_tip,
			(struct mac_addr *)arp->m_arp.ar_sha, arp->m_arp.ar_sip);

	printf("Replied packet: \n");
	print_packet((uint8_t *)reply, sizeof(struct ethhdr) + sizeof(struct arphdr), F_RED);

	int size = qemu_send_packet_async(
			&s->nc,
			(uint8_t *)reply,//iov->iov_base,
			sizeof(struct ethhdr) + sizeof(struct arphdr),//iov->iov_len,
			tap_send_completed);
	// Prevent the tap from sending the data to the outside of the VM.
	iov->iov_len = 0;

	free(reply);

	if(size > 0)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

// only for debug
static int condition(struct arp_packet *arp)
{
	uint8_t ip[4] = {0x0a, 0x00, 0x07, 0x0c};

	int *ip1 = (int *)ip;
	int *ip2 = (int *)arp->m_arp.ar_tip;

	if(*ip1 == *ip2)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

// Reply ARP request if it matches some conditions.
static int reply_arp(struct TAPState *s, struct iovec *iov)
{
	struct arp_packet *arp;

	arp = (struct arp_packet *) iov->iov_base;

	if(arp->m_arp.ar_op != htons(ARPOP_REQUEST))
	{
		return 0;
	}

	// Only for debug.
	if(condition(arp))
		return send_reply(s, arp, iov);
	else
		return 0;
}

/*
 * @retval: number of arp packets replied.
 */
int reply_arp_v(struct TAPState *s, __const struct iovec *__iovec, int __count)
{
	struct iovec *iov = (struct iovec *)__iovec;
	struct arp_packet *arp = NULL;
	int arp_cnt = 0;

	for(;__count > 0;__count--, iov ++)
	{
		;
		if(iov->iov_len < ETH_HLEN)
		{
			continue;
		}

		arp = (struct arp_packet *)iov->iov_base;

		if(arp->m_eth.h_proto == htons(ETH_P_ARP))
		{
			print_packet((uint8_t *)iov->iov_base, iov->iov_len, F_RED);
			arp += reply_arp(s, iov);
		}
	}

	return arp_cnt;
}
